package org.budo.sso;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Map;

import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;

import org.budo.dubbo.protocol.http.client.BudoDubboProtocolHttpClient;
import org.budo.support.freemarker.FreemarkerUtil;
import org.budo.support.javax.sql.util.JdbcUtil;
import org.budo.support.lang.util.MapUtil;
import org.budo.support.lang.util.StringUtil;
import org.budo.support.lang.util.UuidUtil;
import org.budo.support.mvcs.Mvcs;
import org.budo.support.servlet.util.ResponseUtil;
import org.budo.support.spring.io.util.ResourceUtil;
import org.budo.support.spring.util.SpringUtil;

import com.alibaba.dubbo.rpc.RpcContext;
import com.alibaba.dubbo.rpc.service.GenericService;

import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

/**
 * @author lmw
 * @see org.budo.sso.config.BudoSsoConfigurer
 */
@Slf4j
@Getter
@Setter
public class BudoStandardSsoFilter extends AbstractBudoSsoFilter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info("#21 init, this=" + this + ", filterConfig=" + filterConfig);
    }

    @Override
    public void destroy() {
        log.info("#31 destroy, this=" + this);
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        String requestURI = Mvcs.getRequestURI(request);
        log.info("#26 doFilter, request=" + requestURI + ", this=" + this + ", request=" + request);

        if (StringUtil.endsWith(requestURI, "/sso/authorize")) {
            this.authorize(request, response);
            return;
        }

        if (StringUtil.endsWith(requestURI, "/sso/redirect_uri")) {
            this.redirect_uri(request, response);
            return;
        }

        chain.doFilter(request, response);
    }

    private void redirect_uri(ServletRequest request, ServletResponse response) {
        String code = request.getParameter("code");

        if (null == code || code.isEmpty()) {
            ResponseUtil.writeToResponse(response, "#71 code=" + code);
            return;
        }

        Map<String, Object> token = this.codeToToken(code);
        if (null == token) {
            ResponseUtil.writeToResponse(response, "#77 token=" + token + ", code=" + code);
            return;
        }

        Integer accountId = (Integer) token.get("accountId");
        String appKey = SpringUtil.getPropertyValue("budo.sso.appKey");

        List<String> permissions = this.listPermissionNameByAccountId(accountId);
        if (null == permissions || !permissions.contains(appKey + "-permission")) {
            ResponseUtil.writeToResponse(response, "#60 no " + appKey + "-permission, permissions=" + permissions + ", accountId=" + accountId + ", appKey=" + appKey);
            return;
        }

        String _token = this.saveSession(accountId);

        Mvcs.addCookie((HttpServletResponse) response, "_token", _token);

        // org.budo.graph.view.util.GraphAuthorizeUtil.SESSION_USER_KEY
        Mvcs.setSessionAttribute(request, "budo-graph-session-user", accountId + "");

        // 登录后跳转
        this.redirectAfterAuthenticationSuccess((HttpServletRequest) request, (HttpServletResponse) response);
    }

    protected void redirectAfterAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response) {
        ResponseUtil.sendRedirect(response, "/");
    }

    protected String saveSession(Integer accountId) {
        DataSource dataSource = (DataSource) SpringUtil.getBean("dataSource");
        String randomUuid = UuidUtil.randomUuid();
        Object[] parameters = { accountId, randomUuid };
        JdbcUtil.executeUpdate(dataSource, "insert into t_token(user_id, text) values (?, ?)", parameters);
        return randomUuid;
    }

    private List<String> listPermissionNameByAccountId(Integer accountId) {
        BudoDubboProtocolHttpClient httpClient = SpringUtil.getBean(BudoDubboProtocolHttpClient.class);
        if (null == httpClient) {
            log.error("#102 httpClient in spring is null, accountId=" + accountId);
            return null;
        }

        GenericService platformPermissionService = (GenericService) httpClient.getReference("com.taojin.account.web.api.PermissionApi");

        String[] parameterTypes = new String[] { Integer.class.getName() };
        Object[] args = new Object[] { accountId };

        RpcContext.getContext().setAttachment("@SignCheck", "true");
        List<String> permissions = (List<String>) platformPermissionService.$invoke("listPermissionNameByAccountId_1", parameterTypes, args);
        return permissions;
    }

    private Map<String, Object> codeToToken(String code) {
        BudoDubboProtocolHttpClient httpClient = SpringUtil.getBean(BudoDubboProtocolHttpClient.class);
        if (null == httpClient) {
            log.error("#119 httpClient in spring is null");
            return null;
        }

        GenericService tokenApi = (GenericService) httpClient.getReference("com.taojin.account.web.api.TokenApi");

        String[] parameterTypes = new String[] { String.class.getName() };
        Object[] args = new Object[] { code };

        RpcContext.getContext().setAttachment("@SignCheck", "true");
        Map<String, Object> token = (Map<String, Object>) tokenApi.$invoke("codeToToken", parameterTypes, args);
        return token;
    }

    private void authorize(ServletRequest request, ServletResponse response) {
        String ssoUrl = SpringUtil.getPropertyValue("budo.sso.url");
        if (StringUtil.isEmpty(ssoUrl)) {
            log.error("#145 no budo.sso.url, show local login page");

            InputStream inputStream = ResourceUtil.classPathResourceInputStream("login.htm");
            Map<String, Object> dataMap = MapUtil.stringObjectMap("message", "#148 no budo.sso.url, show local login page");
            FreemarkerUtil.freemarkerRender(inputStream, dataMap, ResponseUtil.getOutputStream(response)); // 渲染默认的 登录页
            return;
        }

        String appKey = SpringUtil.getPropertyValue("budo.sso.appKey");
        String redirectUri = _buildRedirectUriPrefix((HttpServletRequest) request) + "/sso/redirect_uri";

        String redirectUrl = ssoUrl + "/authorize"//
                + "?app_key=" + appKey //
                + "&redirect_uri=" + redirectUri;
        ResponseUtil.sendRedirect(response, redirectUrl);
    }
}